package algo3c3g3.model;

import java.util.Iterator;
import java.util.List;


import algo3c3g3.model.material.*;
import org.jdom.Element;

import algo3c3g3.model.persistence.Persistable;

/**
 * Representa un mapa del juego como un conjunto de bloques de distintos materiales.
 */
public class WorldMap implements Persistable
{   
    private Block blocks[][];
    
    private int height, width;
    
    /**
     * Crea un mapa con la dimension indicada.
     * @param width Ancho (numero de columnas) del mapa.
     * @param height Altura (numero de filas) del mapa.
     */
    public WorldMap(int width, int height)
    {
        blocks = new Block[height][width];
        
        this.height = height;
        this.width = width;
    }
    
    public WorldMap(Element element) {
            this.load(element);
    }

    /**
     * Devuelve la altura (numero de filas) del mapa.
     */
    public int getHeight()
    {
        return height;
    }
    
    /**
     * Devuelve el ancho (numero de columnas) del mapa.
     */
    public int getWidth()
    {
        return width;
    }
    
    /**
     * Devuelve una referencia al bloque en la posicion indicada.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public synchronized Block blockAt(int x, int y)
    {
        /* En el array el primer indice es el numero de fila y el segundo
         * es el numero de columna. Se accede al bloque como una coordenada x, y.
         * x es es la posicion horizontal (columna). y es la posicion
         * vertical (fila) */
        try
        {
            return blocks[y][x];
        }
        catch(IndexOutOfBoundsException e)
        {
            throw new BadMapPositionException();
        }
    }
    
    /**
     * Devuelve una referencia al bloque en la posicion indicada.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public synchronized Block blockAt(Coord position)
    {
        return blockAt(position.getXCoord(), position.getYCoord());
    }
    
    /**
     * Devuelve true si el bloque en la posicion indicada existe en el mapa.
     */
    public boolean blockExists(int x, int y)
    {
        try
        {
            blockAt(x, y);
        }
        catch(BadMapPositionException e)
        {
            return false;
        }
        
        return true;
    }
    
    /**
     * Devuelve true si el bloque en la posicion indicada existe en el mapa.
     */
    public boolean blockExists(Coord position)
    {
        return blockExists(position.getXCoord(), position.getYCoord());
    }
    
    /**
     * Reemplaza el bloque en la posicion indicada por uno nuevo
     * del material especificado.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public void replaceBlock(int x, int y, Material material)
    {
        try
        {
            blocks[y][x] = new Block(x, y, material);
        }
        catch(IndexOutOfBoundsException e)
        {
            throw new BadMapPositionException();
        }
    }
    
    /**
     * Reemplaza el bloque en la posicion indicada por uno nuevo
     * del material especificado.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public void replaceBlock(Coord position, Material material)
    {
        replaceBlock(position.getXCoord(), position.getYCoord(), material);
    }
    
    
    /**
     * Destruye el bloque de la posicion indicada si su material es edestructible.
     * El bloque es reemplazado por un bloque de Aire.
     * No se devuelve resultado indicando si el bloque pudo destruirse o no.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public void breakBlock(int x, int y)
    {
        if(blockAt(x, y).isBreakable())
            replaceBlock(x, y,MaterialFactory.getInstance(Material.AIR));
    }
    
    /**
     * Destruye el bloque de la posicion indicada si su material es edestructible.
     * El bloque es reemplazado por un bloque de Aire.
     * No se devuelve resultado indicando si el bloque pudo destruirse o no.
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public void breakBlock(Coord position)
    {
        breakBlock(position.getXCoord(), position.getYCoord());
    }
    
    /**
     * Imprime una representacion del mapa basado en las propiedades de los bloques.
     * Solidos "::"
     * No solidos "  "
     * Irrompibles "||"
     */
    public void printMap()
    {
        for(int i = 0; i < height; i++)
        {
            for(int j = 0; j < width; j++)
            {
                if((blocks[i][j].isSolid()) && (blocks[i][j].isBreakable() == false))
                    System.out.print("||");
                else if((blocks[i][j].isSolid()) && (blocks[i][j].isBreakable()))
                    System.out.print("::");
                else
                    System.out.print("  ");
            }
            
            System.out.println();
        }
    }
    
    /* 
     * Esta funcion genera un mapa de prueba con todos los bloques de tierra.
     */
    protected void fillMap()
    {
        for(int i = 0; i < height; i++)
            for(int j = 0; j < width; j++)
                blocks[i][j] = new Block(j, i, MaterialFactory.getInstance(Material.GROUND));
    }
    
    public void fillWithAir()
    {
        for(int i = 0; i < height; i++)
            for(int j = 0; j < width; j++)
                blocks[i][j] = new Block(j, i, MaterialFactory.getInstance(Material.AIR));
    }
    
    public Element save()
    {
        Element map = new Element("WorldMap");
        map.addContent(new Element("height").setText(Integer.toString(this.getHeight())));
        map.addContent(new Element("width").setText(Integer.toString(this.getWidth())));
       
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width; j++)
            {
            	map.addContent(this.blockAt(j, i).save());
            }
        }

        return map;
    }

    /**
     * Agrega un bloque de acuerdo al valor x,y de este bloque
     * Si la posicion no es valida o no existe en el mapa se lanza
     * una WorldMap.BadMapPositionException().
     */
    public void addBlock(Block block)
    {
        try
        {
            blocks[block.getCoord().getYCoord()][block.getCoord().getXCoord()] = block;
        }
        catch(IndexOutOfBoundsException e)
        {
            throw new BadMapPositionException();
        }
    }
    
    
    public void load(Element e)
    {
    	this.height = Integer.parseInt(e.getChild("height").getText()); 
    	this.width = Integer.parseInt(e.getChild("width").getText());
    	blocks = new Block[height][width];
    	List<Element> blocks = e.getChildren("Block");
    	Iterator i = blocks.iterator();
    	Block auxb;
    	Element auxe;
    	int x,y;
    	while(i.hasNext()){
    		auxe = (Element)i.next();
    		auxb = new Block(auxe);
    		System.out.println(auxb.getCoord().toString());
    		this.addBlock(auxb);
    	}
    }
    
    /**
     * Excepcion para indicar que se intento acceder a un bloque que no existe en el mapa.
     */
    public static class BadMapPositionException extends RuntimeException {}
}
    